/* Copyright 2010 by Matt Van Der Westhuizen */

package org.chaoticengine.ast.view;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.chaoticengine.ast.GlobalData;
import org.chaoticengine.ast.model.AirShip;
import org.chaoticengine.ast.model.AirShipFactory;
import org.chaoticengine.ast.model.Location;
import org.chaoticengine.ast.model.ShipLocation;
import org.chaoticengine.ast.model.WorldMap;
import org.chaoticengine.ast.model.WorldMap.LocationEdge;
import org.chaoticengine.ast.pattern.ViewController;
import org.chaoticengine.cgll.camera.Camera;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.StateBasedGame;

/**
 *
 * @author Matt Van Der Westhuizen <mattpwest@gmail.com>
 */
public class WorldMapView extends ViewController<WorldMap> {
    protected WorldMap map = null;
    protected int tileW = TileFactory.getInstance().getTileWidth();
    protected int tileH = TileFactory.getInstance().getTileHeight();
    protected int mapW = 0;
    protected int mapH = 0;
    protected int scrollDistance = 0;
    protected Camera cam = new Camera();
    protected Vector2f scroll = new Vector2f();
    protected ArrayList<ArrayList<ViewController>> gridView = null;
    protected ArrayList<ArrayList<Location>> gridModel = null;
    protected ArrayList<AirShip> shipModels = new ArrayList<AirShip>();
    protected ArrayList<ViewController> shipViews = new ArrayList<ViewController>();

    public WorldMapView() {}

    public void refresh(WorldMap model) {
        this.map = model;

        if (gridView != null) {
            return;
        }

        // Locations
        this.gridView = new ArrayList<ArrayList<ViewController>>();
        for (int y = 0; y < map.getHeight(); y++) {
            ArrayList<ViewController> row = new ArrayList<ViewController>();
            gridView.add(row);
            for (int x = 0; x < map.getWidth(); x++) {
                Location loc = map.getLocation(x, y);
                if (loc != null) {
                    LocationMapView view = new LocationMapView();
                    loc.addObserver(view);
                    row.add(view);
                } else {
                    row.add(null);
                }
            }
        }

        // Ships
        if (shipViews.size() != AirShipFactory.getInstance().getShips().size()) {
            for (int i = 0; i < shipViews.size(); i++) {
                ViewController<AirShip> shipView = shipViews.get(i);
                shipView.getModel().removeObsever(shipView);
            }
            shipViews.clear();

            Iterator<AirShip> shipIt = AirShipFactory.getInstance().getShips().iterator();
            while (shipIt.hasNext()) {
                AirShipMapView sv = new AirShipMapView();
                AirShip ship = shipIt.next();
                ship.addObserver(sv);
                shipViews.add(sv);
            }
        }

        cam.setBoundingRectangle(new Rectangle(-tileW, -tileH, map.getWidth() * tileW + tileW, map.getHeight() * tileH + tileH));
    }

    public WorldMap getModel() {
        return(this.map);
    }

    @Override
    public void update(GameContainer gc, StateBasedGame sbg, int delta) {
        if (scroll.length() > 0) {
            Vector2f tp = cam.getTargetPosition();
            if (tp == null) {
                tp = cam.getPosition();
            }
            tp.add(scroll.copy().scale(delta / 1000.0f));
            cam.goTo(tp);
        }

        cam.update(gc, sbg, delta);
    }

    public void render(GameContainer gc, StateBasedGame sbg, Graphics grphcs) throws SlickException {
        cam.renderBefore(gc, sbg, grphcs);

        grphcs.setColor(Color.black);
        grphcs.fillRect(cam.getPosition().x,
                cam.getPosition().y,
                cam.getViewport().getWidth(),
                cam.getViewport().getHeight());

        // Only render Locations in the viewport
        int sy = Math.max(0, (int) cam.getPosition().y / tileH);
        int sx = Math.max(0, (int) cam.getPosition().x / tileW);
        int ey = Math.min(map.getHeight(), sy + (int) (cam.getViewport().getHeight() + tileH) / tileH + 1);
        int ex = Math.min(map.getWidth(), sx + (int) (cam.getViewport().getWidth() + tileW) / tileW + 1);

        ArrayList<ViewController> locViews = new ArrayList<ViewController>();
        for (int y = sy; y < ey; y++) {
            ArrayList<ViewController> views = gridView.get(y);
            for (int x = sx; x < ex; x++) {
                ViewController r = views.get(x);

                if (r != null) {
                    locViews.add(r);
                }
            }
        }

        //// Find paths in the view
        ArrayList<LocationEdge> pathViews = new ArrayList<LocationEdge>();
        for (int i = 0; i < locViews.size(); i++) {
            Iterator<LocationEdge> locPaths = map.getPaths((Location) locViews.get(i).getModel());

            while (locPaths.hasNext()) {
                LocationEdge edge = locPaths.next();

                if (!pathViews.contains(edge)) {
                    pathViews.add(edge);
                }
            }
        }

        //// Render paths, then locations
        AirShip ship = GlobalData.getInstance().getPlayerShip();
        List<ShipLocation> route = ship.getRoute();
        for (int i = 0; i < pathViews.size(); i++) {
            LocationEdge e = pathViews.get(i);
            Location src = (Location) e.getSource();
            Location dst = (Location) e.getTarget();

            if (route.contains(e)) {
                grphcs.setColor(Color.red);
                grphcs.setLineWidth(2.0f);
            } else {
                grphcs.setColor(Color.lightGray);
                grphcs.setLineWidth(1.0f);
            }

            grphcs.drawLine(src.getX() * tileW + tileW / 2,
                    src.getY() * tileH + tileH / 2,
                    dst.getX() * tileW + tileW / 2,
                    dst.getY() * tileH + tileH / 2);
        }

        for (int i = 0; i < locViews.size(); i++) {
            locViews.get(i).render(gc, sbg, grphcs);
        }

        // Render all ships (lazy :P)
        for (int i = 0; i < shipViews.size(); i++) {
            shipViews.get(i).render(gc, sbg, grphcs);
        }
        
        cam.renderAfter(gc, sbg, grphcs);
    }

    @Override
    public void onMouseClick(int x, int y, int button) {
        if (!hasFocus) return;

        int cx = (int) cam.getPosition().getX();
        int cy = (int) cam.getPosition().getY();
        ViewController vc = null;

        try {
            vc = gridView.get((cy + y) / tileH).get((cx + x) / tileW);
        } catch (IndexOutOfBoundsException ioobe) {
        }

        if (vc != null) {
            if (vc.getBoundingRectangle().contains(cx + x, cy + y)) {
                vc.onMouseClick(cx + x, cy + y, button);
            }
        }

        for (int i = 0; i < shipViews.size(); i++) {
            vc = shipViews.get(i);
            vc.onMouseClick(cx + x, cy + y, button);
        }
    }

    @Override
    public void onMouseEnter() {
        hasFocus = true;
    }

    @Override
    public void onMouseExit() {
        hasFocus = false;
    }

    @Override
    public void onMouseMove(int oldx, int oldy, int newx, int newy) {
        if (!hasFocus) return;
        
        // Handle mouse scrolling
        //mouseScroll(oldx, oldy, newx, newy);

        // Find current & previous ViewControllers over which the mouse is / was
        int cx = (int) cam.getPosition().getX();
        int cy = (int) cam.getPosition().getY();
        int oldTx = (cx + oldx) / tileW;
        int oldTy = (cy + oldy) / tileH;
        int newTx = (cx + newx) / tileW;
        int newTy = (cy + newy) / tileH;

        ViewController oldVc = null;
        ViewController newVc = null;

        try {
            oldVc = gridView.get(oldTy).get(oldTx);
        } catch (IndexOutOfBoundsException ioobe) {
        }

        try {
            newVc = gridView.get(newTy).get(newTx);
        } catch (IndexOutOfBoundsException ioobe) {
        }

        // Forward mouse events to child views
        if (oldVc != null) {
            if ((oldVc.getBoundingRectangle().contains(cx + oldx, cy + oldy)) &&
                    (!oldVc.getBoundingRectangle().contains(cx + newx, cy + newy))) {
                oldVc.onMouseExit();
            }
        }

        if (newVc != null) {
            if ((!newVc.getBoundingRectangle().contains(cx + oldx, cy + oldy)) &&
                    (newVc.getBoundingRectangle().contains(cx + newx, cy + newy))) {
                newVc.onMouseEnter();
            }
        }

        // Forward mouse events for ship views
        for (int i = 0; i < shipViews.size(); i++) {
            ViewController sv = shipViews.get(i);

            if ((sv.getBoundingRectangle().contains(cx + oldx, cy + oldy)) &&
                    (!sv.getBoundingRectangle().contains(cx + newx, cy + newy))) {
                sv.onMouseExit();
            }

            if ((!sv.getBoundingRectangle().contains(cx + oldx, cy + oldy)) &&
                    (sv.getBoundingRectangle().contains(cx + newx, cy + newy))) {
                sv.onMouseEnter();
            }
        }
    }

    @Override
    public void onMouseRelease(int x, int y, int button) {
        if (!hasFocus) return;
        
        int cx = (int) cam.getPosition().getX();
        int cy = (int) cam.getPosition().getY();
        ViewController vc = null;

        // Try Location
        try {
            vc = gridView.get((cy + y) / tileH).get((cx + x) / tileW);
        } catch (IndexOutOfBoundsException ioobe) {
        }

        // Try ships
        ViewController<AirShip> shipVc = null;
        for (int i = 0; i < shipViews.size(); i++) {
            shipVc = shipViews.get(i);
            if (shipVc.getBoundingRectangle().contains(cx +x, cy + y)) {
                vc = shipVc;
            }
        }

        if (vc != null) { // Select
            vc.onMouseRelease(cx + x, cy + y, button);
        } else { // Deselect
            // Only deselect on LMB
            if (button == Input.MOUSE_LEFT_BUTTON) {
                GlobalData.getInstance().setSelected(null);
            }
        }
    }

    @Override
    public void onMouseWheelDown() {
        if (!hasFocus) return;
    }

    @Override
    public void onMouseWheelUp() {
        if (!hasFocus) return;
    }

    @Override
    public void keyPressed(int key, char c) {
        if (key == Input.KEY_LEFT) {
            scroll = scroll.add(new Vector2f(-scrollDistance, 0));
        } else if (key == Input.KEY_RIGHT) {
            scroll = scroll.add(new Vector2f(scrollDistance, 0));
        } else if (key == Input.KEY_UP) {
            scroll = scroll.add(new Vector2f(0, -scrollDistance));
        } else if (key == Input.KEY_DOWN) {
            scroll = scroll.add(new Vector2f(0, scrollDistance));
        } else if (key == Input.KEY_BACK) {
            GlobalData gd = GlobalData.getInstance();
            AirShip ship = gd.getPlayerShip();
            Vector2f p = ship.getPosition();
            cam.goTo(new Vector2f(p.x * tileW, p.y * tileH));
        }
    }

    @Override
    public void keyReleased(int key, char c) {
        if (key == Input.KEY_LEFT) {
            scroll = scroll.add(new Vector2f(scrollDistance, 0));
        } else if (key == Input.KEY_RIGHT) {
            scroll = scroll.add(new Vector2f(-scrollDistance, 0));
        } else if (key == Input.KEY_UP) {
            scroll = scroll.add(new Vector2f(0, scrollDistance));
        } else if (key == Input.KEY_DOWN) {
            scroll = scroll.add(new Vector2f(0, -scrollDistance));
        }
    }

    private void mouseScroll(int oldx, int oldy, int newx, int newy) {
        int left = (int) (cam.getScreen().getWidth() * 0.02f);
        int right = (int) (cam.getScreen().getWidth() * 0.98f);
        int top = (int) (cam.getScreen().getHeight() * 0.02f);
        int bottom = (int) (cam.getScreen().getHeight() * 0.98f);

        // onEnter
        if (!(oldx < left) && (newx < left)) {
            scroll = scroll.add(new Vector2f(-scrollDistance, 0));
        } else if (!(oldx > right) && (newx > right)) {
            scroll = scroll.add(new Vector2f(scrollDistance, 0));
        }

        if (!(oldy < top) && (newy < top)) {
            scroll = scroll.add(new Vector2f(0, -scrollDistance));
        } else if (!(oldy > bottom) && (newy > bottom)) {
            scroll = scroll.add(new Vector2f(0, scrollDistance));
        }

        // onLeave
        if ((oldx < left) && !(newx < left)) {
            scroll = scroll.add(new Vector2f(scrollDistance, 0));
        } else if ((oldx > right) && !(newx > right)) {
            scroll = scroll.add(new Vector2f(-scrollDistance, 0));
        }

        if ((oldy < top) && !(newy < top)) {
            scroll = scroll.add(new Vector2f(0, scrollDistance));
        } else if ((oldy > bottom) && !(newy > bottom)) {
            scroll = scroll.add(new Vector2f(0, -scrollDistance));
        }
    }

    public int getTileWidth() {
        return(tileW);
    }

    public int getTileHeight() {
        return(tileH);
    }

    /** Scroll distance in pixels per second. */
    public int getScrollDistance() {
        return scrollDistance;
    }

    /** Scroll distance in pixels per second. */
    public void setScrollDistance(int scrollDistance) {
        this.scrollDistance = scrollDistance;
    }

    public Rectangle getViewport() {
        return(cam.getViewport());
    }

    public void setViewport(Rectangle r) {
        cam.setViewport(r);
    }

    public Camera getCamera() {
        return(cam);
    }

    @Override
    public void onDeselect() {
    }

    @Override
    public void onSelect() {
    }
}
